package fr.pud.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.openstreetmap.gui.jmapviewer.Coordinate;

import fr.pud.client.model.PUDStation;

/**
 * This class is an interface between the software and OSRM server which
 * computes distances and coordinates.
 * 10 avr. 2012 - tsp-osm
 * @author Simon Devineau, Adrien Hierry, Leopold Houdin, Pierre Reliquet
 *         Ecole des Mines de Nantes
 *         OSRMTools.java
 */
public class OSRMTools {
    /*
     * Declaration of constants used to build queries.
     */
    // General constants
    public static final String AMPERSAND         = "&";
    public static final String EQUAL             = "=";
    public static final String COMMA             = ",";
    // Server constants
    public static final String IP                = "172.17.2.181";
    // public static final String IP = "127.0.0.1";
    public static final String SERVER            = "http://" + OSRMTools.IP
                                                         + ":5000/";
    // General query constants
    public static final String OUTPUT            = "output" + OSRMTools.EQUAL
                                                         + "json";
    public static final String VIAROUTE          = "viaroute"
                                                         + OSRMTools.AMPERSAND
                                                         + OSRMTools.OUTPUT
                                                         + OSRMTools.AMPERSAND;
    public static final String NEAREST           = "nearest"
                                                         + OSRMTools.AMPERSAND
                                                         + OSRMTools.OUTPUT
                                                         + OSRMTools.AMPERSAND;
    public static final String START             = "start";
    public static final String DEST              = "dest";
    // Regex constants used to parse returned string by the server
    public static final String REGEX_ANYTHING    = ".*";
    public static final String REGEX_COORD       = "(\\-?\\d+\\.\\d+)";
    public static final String REGEX_NUMBER      = "(\\d+)";
    public static final String REGEX_COORD_OPEN  = "\"coordinate\": \\[";
    public static final String REGEX_COORD_CLOSE = "\\]";
    public static final String REGEX_TIME_OPEN   = "\"total_time\":";
    public static final String REGEX_TIME_CLOSE  = OSRMTools.COMMA;

    /**
     * Computes the distance between two points (i.e. two coordinates).
     * Be careful, the path returned is oriented (i.e. path(a,b)!=path(b,a)).
     * @param a
     *            the first coordinate.
     * @param b
     *            the second coordinate.
     * @return the distance between the two coordinates.
     */
    public static double computeDistanceBetween2Coordinates(Coordinate a,
            Coordinate b) {
        try {
            BufferedReader buffer = new BufferedReader(new InputStreamReader(
                    new URL(OSRMTools.SERVER + OSRMTools.VIAROUTE
                            + OSRMTools.START + OSRMTools.EQUAL + a.getLat()
                            + OSRMTools.COMMA + a.getLon()
                            + OSRMTools.AMPERSAND + OSRMTools.DEST
                            + OSRMTools.EQUAL + b.getLat() + OSRMTools.COMMA
                            + b.getLon()).openStream()));
            String line = null;
            while ((line = buffer.readLine()) != null) {
                Matcher m = Pattern.compile(
                        OSRMTools.REGEX_ANYTHING + OSRMTools.REGEX_TIME_OPEN
                                + OSRMTools.REGEX_NUMBER
                                + OSRMTools.REGEX_TIME_CLOSE
                                + OSRMTools.REGEX_ANYTHING).matcher(line);
                if (m.matches()) {
                    buffer.close();
                    return Double.parseDouble(m.group(1));
                }
            }
            buffer.close();
        }
        catch (MalformedURLException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return 0.0;
    }

    /**
     * Computes the distance matrix with ORSM server.
     * @param nodes
     *            coordinates array.
     * @return the distance matrix.
     */
    public static double[][] computeDistanceMatrix(ArrayList<PUDStation> nodes) {
        int size = nodes.size();
        double[][] matrix = new double[size][size];
        long start = new Date().getTime();
        int op_max = size * size, op_curr = 0, d_count = 0;
        double pt_curr = 0.0;
        System.out.println("----------------------------------------");
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                if (i != j) {
                    matrix[i][j] = OSRMTools
                            .computeDistanceBetween2Coordinates(nodes.get(i)
                                    .getAddress().getCoordinate(), nodes.get(j)
                                    .getAddress().getCoordinate());
                }
                else {
                    matrix[i][j] = 0.0;
                }
                op_curr++;
                pt_curr = (double) op_curr / (double) op_max;
                if (pt_curr - d_count * 0.025 > 0.025) {
                    d_count++;
                    System.out.print("#");
                }
            }
        }
        long end = new Date().getTime();
        System.out.println("#");
        System.out.println("Matrix computed in "
                + OSRMTools.getDurationString(end - start));
        return matrix;
    }

    /**
     * Compute the nearest node to the given node which is on a road.
     * @param node
     *            the node to be computed.
     */
    public static void computeNearestPoint(PUDStation node) {
        try {
            BufferedReader buffer = new BufferedReader(new InputStreamReader(
                    new URL(OSRMTools.SERVER + OSRMTools.NEAREST
                            + node.getAddress().getCoordinate().getLat()
                            + OSRMTools.AMPERSAND
                            + node.getAddress().getCoordinate().getLon())
                            .openStream()));
            String line = null;
            while ((line = buffer.readLine()) != null) {
                Matcher m = Pattern.compile(
                        OSRMTools.REGEX_ANYTHING + OSRMTools.REGEX_COORD_OPEN
                                + OSRMTools.REGEX_COORD + OSRMTools.COMMA
                                + OSRMTools.REGEX_COORD
                                + OSRMTools.REGEX_COORD_CLOSE
                                + OSRMTools.REGEX_ANYTHING).matcher(line);
                if (m.matches()) {
                    node.getAddress().getCoordinate()
                            .setLat(Double.parseDouble(m.group(1)));
                    node.getAddress().getCoordinate()
                            .setLon(Double.parseDouble(m.group(2)));
                }
            }
            buffer.close();
        }
        catch (MalformedURLException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Returns a string of the given integer duration.
     * @param duration
     *            the integer duration.
     * @return the string.
     */
    public static String getDurationString(long duration) {
        String s = "";
        long tmp = duration;
        s += tmp / 60000 + "m ";
        tmp = tmp % 60000;
        s += tmp / 1000 + "s ";
        tmp = tmp % 1000;
        s += tmp + "ms";
        return s;
    }
}
